package com.md.config.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.RequiredArgsConstructor;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;

/**
 * @author 刘骄阳
 */
@Configuration
@EnableCaching
@ConditionalOnClass({RedisConnectionFactory.class})
@RequiredArgsConstructor
public class RedisConfig extends CachingConfigurerSupport {
  /**
   * 默认日期时间格式
   */
  private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
  /**
   * 默认日期格式
   */
  private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
  /**
   * 默认时间格式
   */
  private static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

  /**
   * 300秒的缓存有效期
   */
  private static final int DEFAULT_EXPIRE_TIME = 300;
  private final RedisProperty redisProperty;

  /**
   * 生产key的策略
   */
  @Bean
  @Override
  public KeyGenerator keyGenerator() {
    return (target, method, params) -> {
      StringBuilder sb = new StringBuilder();
      sb.append(target.getClass().getName());
      sb.append(method.getName());
      for (Object obj : params) {
        sb.append(obj.toString());
      }
      return sb.toString();
    };

  }

  /**
   * redis 数据库连接池
   */
  @Bean
  public RedisConnectionFactory redisConnectionFactory() {
    RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
    //设置主机
    redisConfig.setHostName(redisProperty.getHost());
    //设置密码
    redisConfig.setPassword(redisProperty.getPassword());
    //设置端口
    redisConfig.setPort(redisProperty.getPort());
    //设置连接库
    redisConfig.setDatabase(redisProperty.getDataBase());
    //获取默认的连接池构造器
    JedisClientConfiguration.JedisPoolingClientConfigurationBuilder redisPoolingBuilder =
      (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
    JedisPoolConfig config = new JedisPoolConfig();
    //设置最大连接数
    config.setMaxTotal(redisProperty.getMaxTotal());
    //设置最大空闲连接数
    config.setMaxIdle(redisProperty.getMaxIdle());
    //设置最小空闲连接数
    config.setMinIdle(redisProperty.getMinIdle());
    //设置 连接耗尽进入阻塞
    config.setBlockWhenExhausted(redisProperty.getIsBlocked());
    //阻塞等待时间
    config.setMaxWaitMillis(redisProperty.getMaxWaitTime());
    //从连接池获取对象时,是否进行检查
    config.setTestOnBorrow(redisProperty.getTestOnBorrow());
    //归还连接时,是否进行检查
    config.setTestOnReturn(redisProperty.getTestOnReturn());
    //修改默认的连接池构造器
    redisPoolingBuilder.poolConfig(config);
    return new JedisConnectionFactory(redisConfig, redisPoolingBuilder.build());
  }

  /**
   * redisson配置
   *
   * @return redisson
   */
  @Bean
  public RedissonClient redisson() {
    Config config = new Config();
    config.useSingleServer().setAddress(redisProperty.getRedissonAddress()).setPassword(redisProperty.getPassword());
    return Redisson.create(config);
  }

  /**
   * redisTemplate配置
   */
  @Bean(name = "customRedisTemplate")
  public RedisTemplate<String, String> redisTemplate(final RedisConnectionFactory factory) {
    StringRedisTemplate customRedisTemplate = new StringRedisTemplate(factory);
    Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
      ObjectMapper.DefaultTyping.NON_FINAL);

    //LocalDateTime系列序列化和反序列化模块
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.
      ofPattern(DEFAULT_DATE_TIME_FORMAT)));
    javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.
      ofPattern(DEFAULT_DATE_FORMAT)));
    javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.
      ofPattern(DEFAULT_TIME_FORMAT)));
    javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.
      ofPattern(DEFAULT_DATE_TIME_FORMAT)));
    javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.
      ofPattern(DEFAULT_DATE_FORMAT)));
    javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.
      ofPattern(DEFAULT_TIME_FORMAT)));
    objectMapper.registerModule(javaTimeModule);
    serializer.setObjectMapper(objectMapper);

    // 设置序列化和反序列化
    customRedisTemplate.setKeySerializer(new StringRedisSerializer());
    customRedisTemplate.setValueSerializer(serializer);
    customRedisTemplate.setHashKeySerializer(new StringRedisSerializer());
    customRedisTemplate.setHashValueSerializer(serializer);
    customRedisTemplate.afterPropertiesSet();
    return customRedisTemplate;
  }

  @Bean
  RedisMessageListenerContainer container() {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(redisConnectionFactory());
    return container;
  }

  @Bean
  public RedisCacheManager redisCacheManager(final RedisTemplate<String, String> customRedisTemplate) {
    RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
      // 失效时间
      .entryTtl(Duration.ofSeconds(DEFAULT_EXPIRE_TIME))
      // 前缀
      .computePrefixWith(cacheName -> cacheName).
      // 序列化key
        serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
      // 序列化值
      .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(customRedisTemplate.
        getValueSerializer()))
      // 不缓存null值
      .disableCachingNullValues();
    return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(Objects.
        requireNonNull(customRedisTemplate.getConnectionFactory())))
      .cacheDefaults(defaultCacheConfig)
      .build();

  }

}
